package config

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"

	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkecommon"
	configv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	configinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	"gopkg.openfuyao.cn/bkecommon/security"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	yaml2 "sigs.k8s.io/yaml"

	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/root"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

type Options struct {
	root.Options
	Directory     string   `json:"directory"`
	File          string   `json:"file"`
	Args          []string `json:"args"`
	Product       string   `json:"product"`
	Domain        string   `json:"domain"`
	ImageRepoPort string   `json:"imageRepoPort"`
}

// GenerateControllerParam generate containerd needed params
func GenerateControllerParam(domain string) (string, string) {
	sandbox := fmt.Sprintf("%s/%s/%s:%s", domain, bkecommon.ImageRegistryKubernetes,
		configinit.DefaultPauseImageName, configinit.DefaultPauseImageTag)
	offline := "false"
	if global.CustomExtra["otherRepo"] != "" && !strings.Contains(global.CustomExtra["otherRepo"], domain) {
		repoPrefixList := strings.Split(global.CustomExtra["otherRepo"], "/")
		repoPrefix := strings.Join(repoPrefixList[:len(repoPrefixList)-1], "/")
		sandbox = fmt.Sprintf("%s/%s/%s:%s", repoPrefix, bkecommon.ImageRegistryKubernetes,
			configinit.DefaultPauseImageName, configinit.DefaultPauseImageTag)
	}
	if global.CustomExtra["otherRepo"] == "" {
		offline = "true"
	}
	return sandbox, offline
}

func (op *Options) Config(customExtra map[string]string, imageRepo, yumRepo confv1beta1.Repo, ntpServer string) {
	if err := op.ensureDirectory(); err != nil {
		return
	}

	bkeCluster := op.createBKECluster()
	cfg := op.initBaseConfig(customExtra, imageRepo, yumRepo, ntpServer)

	op.optimizeKubeClient(cfg)
	op.applyBocCustomConfig(cfg, yumRepo)

	op.generateConfigFiles(cfg, bkeCluster)

	log.BKEFormat(log.INFO, fmt.Sprintf("Generate the bkecluster configuration file directory %s", op.Directory))
}

// ensureDirectory 确保目录存在
func (op *Options) ensureDirectory() error {
	if utils.Exists(op.Directory) {
		return nil
	}

	if err := os.MkdirAll(op.Directory, 0644); err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Unable to create directory %v", err))
		return err
	}
	return nil
}

// createBKECluster 创建基础的 BKECluster 对象
func (op *Options) createBKECluster() configv1beta1.BKECluster {
	return configv1beta1.BKECluster{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "bke.bocloud.com/v1beta1",
			Kind:       "BKECluster",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      "bke-cluster",
			Namespace: "bke-cluster",
		},
		Spec: configv1beta1.BKEClusterSpec{
			Pause:         false,
			DryRun:        false,
			Reset:         false,
			ClusterConfig: nil,
			KubeletConfigRef: &confv1beta1.KubeletConfigRef{
				Name:      "bke-kubelet",
				Namespace: "bke-kubelet",
			},
		},
	}
}

// initBaseConfig 初始化基础配置
func (op *Options) initBaseConfig(customExtra map[string]string, imageRepo, yumRepo confv1beta1.Repo,
	ntpServer string) *configv1beta1.BKEConfig {
	cfg, _ := configinit.ConvertBkEConfig(configinit.GetDefaultBKEConfig())

	op.applyCustomConfig(cfg, customExtra, imageRepo, yumRepo, ntpServer)

	return cfg
}

// applyCustomConfig 应用自定义配置
func (op *Options) applyCustomConfig(cfg *configv1beta1.BKEConfig, customExtra map[string]string,
	imageRepo, yumRepo confv1beta1.Repo, ntpServer string) {
	if len(customExtra) > 0 {
		cfg.CustomExtra = customExtra
	}

	if len(imageRepo.Domain) > 0 {
		cfg.Cluster.ImageRepo = imageRepo
	}

	if len(yumRepo.Domain) > 0 {
		cfg.Cluster.HTTPRepo = yumRepo
	}

	if len(ntpServer) > 0 {
		cfg.Cluster.NTPServer = ntpServer
	}
}

// optimizeKubeClient 优化 Kubernetes 客户端配置
func (op *Options) optimizeKubeClient(cfg *configv1beta1.BKEConfig) {
	cfg.Cluster.APIServer = &confv1beta1.APIServer{
		ControlPlaneComponent: confv1beta1.ControlPlaneComponent{
			ExtraArgs: map[string]string{
				"max-mutating-requests-inflight": "3000",
				"max-requests-inflight":          "1000",
				"watch-cache-sizes":              "node#1000,pod#5000",
			},
		},
	}

	cfg.Cluster.ControllerManager = &confv1beta1.ControlPlaneComponent{
		ExtraArgs: map[string]string{
			"kube-api-qps":   "1000",
			"kube-api-burst": "1000",
		},
	}

	cfg.Cluster.Scheduler = &confv1beta1.ControlPlaneComponent{
		ExtraArgs: map[string]string{
			"kube-api-qps": "1000",
		},
	}

	cfg.Cluster.Kubelet = &confv1beta1.Kubelet{
		ControlPlaneComponent: confv1beta1.ControlPlaneComponent{
			ExtraArgs: map[string]string{
				"kube-api-qps":   "1000",
				"kube-api-burst": "2000",
			},
			ExtraVolumes: []confv1beta1.HostPathMount{
				{
					Name:     "kubelet-root-dir",
					HostPath: "/var/lib/kubelet",
				},
			},
		},
	}
}

// applyBocCustomConfig 应用初始自定义配置
func (op *Options) applyBocCustomConfig(cfg *configv1beta1.BKEConfig, yumRepo confv1beta1.Repo) {
	cfg.Cluster.KubernetesVersion = "v1.33.1-of.1"
	cfg.Cluster.ContainerdVersion = "v2.1.1"
	cfg.Cluster.OpenFuyaoVersion = "latest"
	cfg.Cluster.ContainerdConfigRef = &configv1beta1.ContainerdConfigRef{
		Namespace: "bke-containerd",
		Name:      "bke-containerd",
	}
	cfg.Cluster.ContainerRuntime = confv1beta1.ContainerRuntime{
		CRI:     "containerd",
		Runtime: "runc",
		Param: map[string]string{
			"data-root": "/var/lib/containerd",
		},
	}

	cfg = op.product(cfg, yumRepo)
}

// generateConfigFiles 生成配置文件
func (op *Options) generateConfigFiles(cfg *configv1beta1.BKEConfig, bkeCluster configv1beta1.BKECluster) {
	master1(op.Directory, bkeCluster, cfg)
	master1node1(op.Directory, bkeCluster, cfg)
	master3(op.Directory, bkeCluster, cfg)
}

func (op *Options) product(cfg *confv1beta1.BKEConfig, yumRepo confv1beta1.Repo) *configv1beta1.BKEConfig {
	sandbox, offline := op.generateControllerParams()

	op.setBaseAddons(cfg)
	op.applyProductSpecificConfig(cfg, sandbox, offline)

	return cfg
}

// generateControllerParams 生成控制器参数
func (op *Options) generateControllerParams() (string, string) {
	return GenerateControllerParam(fmt.Sprintf("%s:%s", op.Domain, op.ImageRepoPort))
}

// setBaseAddons 设置基础插件配置
func (op *Options) setBaseAddons(cfg *confv1beta1.BKEConfig) {
	cfg.Addons = []confv1beta1.Product{
		{
			Name:    "kubeproxy",
			Version: "v1.33.1-of.1",
			Param: map[string]string{
				"clusterNetworkMode": "calico",
			},
		},
		{
			Block:   true,
			Name:    "calico",
			Version: "v3.27.3",
			Param: map[string]string{
				"calicoMode":            "vxlan",
				"ipAutoDetectionMethod": "skip-interface=nerdctl*",
			},
		},
		{
			Name:    "coredns",
			Version: "v1.10.1",
		},
		{
			Name:    "bkeagent-deployer",
			Version: "latest",
			Param: map[string]string{
				"tagVersion": "latest",
			},
		},
	}
}

// applyProductSpecificConfig 应用产品特定配置
func (op *Options) applyProductSpecificConfig(cfg *confv1beta1.BKEConfig, sandbox, offline string) {
	switch op.Product {
	case "fuyao-portal":
		op.applyFuyaoPortalConfig(cfg, sandbox, offline)
	case "fuyao-business":
		// fuyao-business 只需要设置 Kubernetes 版本，已在上面设置
	case "fuyao-allinone":
		op.applyFuyaoAllInOneConfig(cfg, sandbox, offline)
	default:
		op.logUnsupportedProduct()
	}
}

// applyFuyaoPortalConfig 应用扶摇门户配置
func (op *Options) applyFuyaoPortalConfig(cfg *confv1beta1.BKEConfig, sandbox, offline string) {
	clusterAPIAddon := op.createClusterAPIAddon(sandbox, offline)
	systemControllerAddon := op.createSystemControllerAddon()

	cfg.Addons = append(cfg.Addons, clusterAPIAddon, systemControllerAddon)
}

// applyFuyaoAllInOneConfig 应用扶摇全量配置
func (op *Options) applyFuyaoAllInOneConfig(cfg *confv1beta1.BKEConfig, sandbox, offline string) {
	clusterAPIAddon := op.createClusterAPIAddon(sandbox, offline)
	systemControllerAddon := op.createSystemControllerAddon()

	cfg.Addons = append(cfg.Addons, clusterAPIAddon, systemControllerAddon)
}

// createClusterAPIAddon 创建集群 API 插件
func (op *Options) createClusterAPIAddon(sandbox, offline string) confv1beta1.Product {
	return confv1beta1.Product{
		Name:    "cluster-api",
		Version: "v1.4.3",
		Block:   true,
		Param: map[string]string{
			"manage":            "true",
			"offline":           offline,
			"sandbox":           sandbox,
			"replicas":          "1",
			"containerdVersion": "v2.1.1",
			"openFuyaoVersion":  "latest",
			"manifestsVersion":  "latest",
			"providerVersion":   "latest",
		},
	}
}

// createSystemControllerAddon 创建系统控制器插件
func (op *Options) createSystemControllerAddon() confv1beta1.Product {
	return confv1beta1.Product{
		Name:    "openfuyao-system-controller",
		Version: "latest",
		Param: map[string]string{
			"helmRepo":   "https://helm.openfuyao.cn/_core",
			"tagVersion": "latest",
		},
	}
}

// logUnsupportedProduct 记录不支持的产品日志
func (op *Options) logUnsupportedProduct() {
	log.BKEFormat(log.WARN, fmt.Sprintf("The product %s is not supported", op.Product))
}

// EncryptFile 加密文件
func (op *Options) EncryptFile() error {
	return op.processPasswordFile(true)
}

// DecryptFile 解密文件
func (op *Options) DecryptFile() error {
	return op.processPasswordFile(false)
}

func (op *Options) processPasswordFile(isEncrypt bool) error {
	conf, err := op.loadClusterConfig()
	if err != nil {
		return err
	}

	newNodes, err := op.processNodePasswords(conf.Spec.ClusterConfig.Nodes, isEncrypt)
	if err != nil {
		return err
	}
	conf.Spec.ClusterConfig.Nodes = newNodes

	return op.saveProcessedConfig(conf, isEncrypt)
}

func (op *Options) loadClusterConfig() (*configv1beta1.BKECluster, error) {
	conf := &configv1beta1.BKECluster{}
	yamlFile, err := os.ReadFile(op.File)
	if err != nil {
		return nil, err
	}

	err = yaml2.Unmarshal(yamlFile, conf)
	if err != nil {
		return nil, err
	}

	if conf.Spec.ClusterConfig == nil {
		return nil, errors.New("cluster configuration is empty. ")
	}

	return conf, nil
}

func (op *Options) processNodePasswords(nodes []confv1beta1.Node, isEncrypt bool) ([]confv1beta1.Node, error) {
	newNodes := make([]confv1beta1.Node, 0, len(nodes))

	for _, node := range nodes {
		processedNode, err := op.processNodePassword(node, isEncrypt)
		if err == nil {
			newNodes = append(newNodes, processedNode)
			continue
		}
	}

	return newNodes, nil
}

func (op *Options) processNodePassword(node confv1beta1.Node, isEncrypt bool) (confv1beta1.Node, error) {
	tmpNode := node

	if isEncrypt {
		return op.encryptNodePassword(tmpNode)
	} else {
		return op.decryptNodePassword(tmpNode)
	}
}

func (op *Options) encryptNodePassword(node confv1beta1.Node) (confv1beta1.Node, error) {
	_, err := security.AesDecrypt(node.Password)
	if err == nil {
		return node, nil
	}

	encryptedPassword, err := security.AesEncrypt(node.Password)
	if err != nil {
		return node, err
	}

	node.Password = encryptedPassword
	return node, nil
}

func (op *Options) decryptNodePassword(node confv1beta1.Node) (confv1beta1.Node, error) {
	decryptedPassword, err := security.AesDecrypt(node.Password)
	if err != nil {
		return node, nil
	}

	node.Password = decryptedPassword
	return node, nil
}

func (op *Options) saveProcessedConfig(conf *configv1beta1.BKECluster, isEncrypt bool) error {
	by, err := yaml2.Marshal(conf)
	if err != nil {
		return err
	}

	pwd, _ := os.Getwd()
	action := "encrypt"
	if !isEncrypt {
		action = "decrypt"
	}

	bkefile := filepath.Join(pwd, fmt.Sprintf("%s-%s.yaml", conf.Name, action))
	err = os.WriteFile(bkefile, by, utils.DefaultFilePermission)
	if err != nil {
		return err
	}

	log.BKEFormat(log.INFO, fmt.Sprintf("%s is complete, %s file in the current directory",
		strings.Title(action), conf.Name+"-"+action+".yaml"))
	return nil
}

func (op *Options) EncryptString() error {
	for _, arg := range op.Args {
		p, err := security.AesEncrypt(arg)
		if err != nil {
			return err
		}
		fmt.Println(p)
	}
	return nil
}

func (op *Options) DecryptString() error {
	for _, arg := range op.Args {
		p, err := security.AesDecrypt(arg)
		if err != nil {
			return err
		}
		fmt.Println(p)
	}
	return nil
}

func master1(directory string, cluster configv1beta1.BKECluster, cfg *confv1beta1.BKEConfig) {
	cfg1 := cfg.DeepCopy()
	cfg1.Nodes = []confv1beta1.Node{
		{
			Hostname: "m1",
			IP:       "127.0.0.2",
			Username: "root",
			Password: "******",
			Port:     "22",
			Role: []string{
				"master/node",
				"etcd",
			},
		},
	}

	cluster.Spec.ClusterConfig = cfg1
	b, err := yaml2.Marshal(cluster)
	if err != nil {
		log.BKEFormat(log.ERROR, err.Error())
		return
	}

	err = os.WriteFile(path.Join(directory, "1master.yaml"), b, utils.DefaultFilePermission)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to write the configuration file. Procedure %v", err))
		return
	}
}

func master1node1(directory string, cluster configv1beta1.BKECluster, cfg *confv1beta1.BKEConfig) {
	cfg1 := cfg.DeepCopy()
	cfg1.Nodes = []confv1beta1.Node{
		{
			Hostname: "m1",
			IP:       "127.0.0.1",
			Username: "u1",
			Password: "******",
			Port:     "22",
			Role: []string{
				"master/node",
				"etcd",
			},
		},
		{
			Hostname: "n1",
			IP:       "127.0.0.2",
			Username: "user2",
			Password: "******",
			Port:     "22",
			Role: []string{
				"node",
			},
		},
	}

	cluster.Spec.ClusterConfig = cfg1
	b, err := yaml2.Marshal(cluster)
	if err != nil {
		log.BKEFormat(log.ERROR, err.Error())
		return
	}

	err = os.WriteFile(path.Join(directory, "1master1node.yaml"), b, utils.DefaultFilePermission)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to write the configuration file. Procedure %v", err))
		return
	}
}

func master3(directory string, cluster configv1beta1.BKECluster, cfg *confv1beta1.BKEConfig) {
	cfg1 := cfg.DeepCopy()
	cfg1.Nodes = []confv1beta1.Node{
		{
			Hostname: "master-1",
			IP:       "127.0.0.1",
			Username: "user1",
			Password: "******",
			Port:     "22",
			Role: []string{
				"master/node",
				"etcd",
			},
		},
		{
			Hostname: "master-2",
			IP:       "127.0.0.2",
			Username: "user2",
			Password: "******",
			Port:     "22",
			Role: []string{
				"master/node",
				"etcd",
			},
		},
		{
			Hostname: "master-3",
			IP:       "127.0.0.3",
			Username: "user3",
			Password: "******",
			Port:     "22",
			Role: []string{
				"master/node",
				"etcd",
			},
		},
	}

	cluster.Spec.ControlPlaneEndpoint = confv1beta1.APIEndpoint{
		Host: "0.0.0.0(vip)",
		Port: 36443,
	}

	cluster.Spec.ClusterConfig = cfg1
	b, err := yaml2.Marshal(cluster)
	if err != nil {
		log.BKEFormat(log.ERROR, err.Error())
		return
	}

	err = os.WriteFile(path.Join(directory, "3master.yaml"), b, utils.DefaultFilePermission)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to write the configuration file. Procedure %v", err))
		return
	}
}
